package cn.bugDance.domain.strategy.service.algorithm.impl;

import cn.bugDance.domain.strategy.model.entity.RuleConfigEntity;
import cn.bugDance.domain.strategy.model.entity.StrategyAwardEntity;
import cn.bugDance.domain.strategy.model.entity.StrategyEntity;
import cn.bugDance.domain.strategy.model.valobj.StrategyConstant;
import cn.bugDance.domain.strategy.repository.IStrategyRepository;
import cn.bugDance.domain.strategy.service.algorithm.IAlgorithm;
import cn.bugDance.types.common.Constants;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.security.SecureRandom;
import java.util.*;
import java.util.stream.Collectors;

@Component("algorithmO1")
@Slf4j
public class AlgorithmO1 implements IAlgorithm {

    @Resource
    private IStrategyRepository strategyRepository;


    @Override
    public void algorithmAssemble(Long strategyId, Date endTime) {
        StrategyEntity strategyEntity = strategyRepository.queryStrategyByStrategyId(strategyId);
        List<StrategyAwardEntity> strategyAwardEntities = strategyRepository.queryStrategyAwardByStrategyId(strategyId);
        if(null == strategyEntity || null == strategyAwardEntities || strategyAwardEntities.isEmpty()) return;

        cacheRaffleRate(strategyAwardEntities, strategyEntity, endTime);
    }

    @Override
    public Integer algorithmQuery(Long strategyId, Integer level) {
        String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyId + ":" + level;
        String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyId + ":" + level;

        Long rateRange = strategyRepository.queryCacheRateRange(cacheRateLevelKey);
        int range = rateRange.intValue();
        return strategyRepository.randomRaffle(new SecureRandom().nextInt(range), cacheRateTableKey);
    }

    @Override
    public Integer algorithmQuery(Long strategyId) {
        String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyId + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;
        String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyId + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;

        Long rateRange = strategyRepository.queryCacheRateRange(cacheRateLevelKey);
        int range = rateRange.intValue();
        return strategyRepository.randomRaffle(new SecureRandom().nextInt(range), cacheRateTableKey);
    }


    private void cacheRaffleRate(List<StrategyAwardEntity> strategyAwardEntities, StrategyEntity strategyEntity, Date endTime){
        if(strategyEntity.isExistRuleModel(StrategyConstant.StrategyRule.RULE_WEIGHT)){
            RuleConfigEntity ruleConfigEntity = strategyRepository.queryRuleConfigRule(strategyEntity.getStrategyId(), StrategyConstant.StrategyRule.RULE_WEIGHT);
            Map<Integer, List<Integer>> levelMap = ruleConfigEntity.ruleConfigSplit();
            if(levelMap.isEmpty()) return;

            Set<Integer> levelSet = levelMap.keySet();
            for(Integer level : levelSet){
                List<Integer> awardIds = levelMap.get(level);
                List<StrategyAwardEntity> strategyAwardEntitiesCopy = strategyAwardEntities;
                strategyAwardEntitiesCopy = strategyAwardEntitiesCopy.stream().filter(strategyAwardEntity -> awardIds.contains(strategyAwardEntity.getAwardId())).collect(Collectors.toList());
                String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyEntity.getStrategyId() + ":" + level;
                String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyEntity.getStrategyId() + ":" + level;
                cacheRaffleRate(strategyAwardEntitiesCopy, cacheRateTableKey, cacheRateLevelKey);
            }
        }
        String cacheRateTableKey = Constants.Redis.STRATEGY_RATE_TABLE + strategyEntity.getStrategyId() + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;
        String cacheRateLevelKey = Constants.Redis.STRATEGY_RATE_LEVEL + strategyEntity.getStrategyId() + ":" + Constants.Redis.STRATEGY_RATE_NO_LEVEL;
        List<StrategyAwardEntity> strategyAwardEntitiesCopy = strategyAwardEntities.stream().filter(strategyAwardEntity -> strategyAwardEntity.getRaffle() == 0).collect(Collectors.toList());
        cacheRaffleRate(strategyAwardEntitiesCopy, cacheRateTableKey, cacheRateLevelKey);

        cacheRaffleAwardCount(strategyAwardEntitiesCopy, endTime);
    }

    private void cacheRaffleAwardCount(List<StrategyAwardEntity> strategyAwardEntitiesCopy, Date endTime) {
        for(StrategyAwardEntity strategyAwardEntity : strategyAwardEntitiesCopy){
            String cacheAwardCountKey = Constants.Redis.STRATEGY_AWARD_COUNT + strategyAwardEntity.getStrategyId() + ":" + strategyAwardEntity.getAwardId();
            strategyRepository.storeAwardCount(cacheAwardCountKey, strategyAwardEntity.getAwardCount(), strategyAwardEntity.getStrategyId(), strategyAwardEntity.getAwardId(), endTime);
        }
    }

    /**
     * 1. 找出最小概率
     * 2. 计算出概率范围
     * 3. 组装奖品表
     * 4. 存入Redis
     * @param strategyAwardEntities
     * @param cacheRateTableKey
     * @param cacheRateLevelKey
     */
    private void cacheRaffleRate(List<StrategyAwardEntity> strategyAwardEntities, String cacheRateTableKey, String cacheRateLevelKey){

        BigDecimal minAwardRate = strategyAwardEntities.stream()
                .map(StrategyAwardEntity::getAwardRate)
                .min(BigDecimal::compareTo)
                .orElse(BigDecimal.ZERO);

        BigDecimal scale = BigDecimal.valueOf(convert(minAwardRate.doubleValue()));

        List<Integer> rateTable = new ArrayList<>();
        for(StrategyAwardEntity strategyAwardEntity : strategyAwardEntities){
            Integer awardId = strategyAwardEntity.getAwardId();
            BigDecimal awardRate = strategyAwardEntity.getAwardRate();

            int rateRange = awardRate.multiply(scale).setScale(0, RoundingMode.CEILING).intValue();
            for(int i = 0; i < rateRange; i++){
                rateTable.add(awardId);
            }
        }
        long rateRangeSum = rateTable.size();
        Collections.shuffle(rateTable);
        strategyRepository.storeToCache(rateTable, rateRangeSum, cacheRateTableKey, cacheRateLevelKey);

    }

    private double convert(double minAwardRate){
        double rateRange = 1;
        while(minAwardRate < 1){
            minAwardRate *= 10;
            rateRange *= 10;
        }
        return rateRange;
    }
}
